using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{

    public class PipelineSerialAssembler : Component
    {
        Dictionary<int, NodeVector> dicAddresses;

        public PipelineBus InputBus;
        public PipelineBus OutputBus;

        public StateMachine oStateMachine;

        PipelineStage oInputStage;
        PipelineStage oOutputStage;
        
        ClockComponent oClock;
        NodeVector nvReset;


        public NodeVector nvAddress;
        int iSequenceLength;

        public PipelineSerialAssembler(Component poParentComponent, string psName, ClockComponent poClock, NodeVector pnvReset, int piWidth, int piAddressLSB, int piAddressWidth, int piSequenceLength)
            : base(poParentComponent, psName)
        {
            InputBus = new PipelineBus(this, "input_bus", piWidth);
            oInputStage = new PipelineStage(this, "input_stage", poClock, pnvReset, piWidth);
            oInputStage.InputBus.Join(InputBus);
            dicAddresses = new Dictionary<int, NodeVector>();
            nvReset = pnvReset;
            oClock = poClock;
            nvAddress = this.CreateNodeVector("address", piAddressWidth);
            nvAddress.Join(oInputStage.OutputBus.Data.Subset(this, piAddressLSB, piAddressLSB + piAddressWidth - 1));
            iSequenceLength = piSequenceLength;
            oOutputStage = new PipelineStage(this, "output_stage", poClock, pnvReset, (piWidth - piAddressWidth) * iSequenceLength);
            OutputBus = new PipelineBus(this, "output_bus", oOutputStage.OutputBus.Data.Width);
            OutputBus.Join(oOutputStage.OutputBus);
        }


        //right to left
        public void DefineSequence(int piAddress)
        {
            NodeVector nvSelect = Operators.EQ(this, piAddress, nvAddress);
            dicAddresses.Add(dicAddresses.Count, nvSelect);
        }

        public void DefineSequence()
        {
            for (int iIdx = 0; iIdx < iSequenceLength; iIdx++)
            {
                DefineSequence(iIdx);
            }
        }

        public override void GenerateStructure()
        {
            //Dictionary<int, ResetReg> dicRegisters = new Dictionary<int, ResetReg>();

            if (dicAddresses.Count == 0)
                DefineSequence();

            NodeVector nvNotDR = !oInputStage.OutputBus.DataReady;

            /* State machine*/
            oStateMachine = new StateMachine(this, "state_machine", oClock);
            oStateMachine.ResetIn.Join(nvReset);

            oStateMachine.AddState("state_reset");
            oStateMachine.AddState("state_stall");
            oStateMachine.AddState("state_final");
            oStateMachine.AddState("state_stall_final");

            oStateMachine.SetDefaultState("state_reset");
            
            int iLSB = 0;
            int iIdx = 0;

            NodeVector nvSelFirst = dicAddresses[0];
            NodeVector nvToFirst = nvSelFirst & oInputStage.OutputBus.DataReady;

            foreach (NodeVector nvSelect in dicAddresses.Values)
            {

                ResetReg oReg = new ResetReg(this, "reg_" + iIdx.ToString(), oClock, InputBus.Data.Width - nvAddress.Width, true, true, false);
                oReg.ResetIn.Join(nvReset);
                NodeVector nvEnable = oInputStage.OutputBus.DataReady & nvSelect;
                oReg.EnableNodes.Join(nvEnable);
                oReg.ResetTo.Join(0);
                // doesn't consider if address is not in MSB position
                oReg.InputNodes.ConnectSubset(oInputStage.OutputBus.Data, 0, oReg.InputNodes.Width - 1);
                //
                oReg.OutputNodes.ConnectSubset(oOutputStage.InputBus.Data, iLSB, iLSB + oReg.InputNodes.Width - 1);
                iLSB += oReg.InputNodes.Width;
                //dicRegisters.Add(iIdx, oReg);

                if (iIdx < dicAddresses.Count - 1)
                {
                    string sState = "state_" + iIdx.ToString();
                    oStateMachine.AddState(sState);
                    int iLastIdx = iIdx - 1;
                    string sPreviousState = "state_" + iLastIdx.ToString();
                    if (iLastIdx < 0)
                        sPreviousState = "state_reset";
                    oStateMachine.StateTransition(sPreviousState, nvEnable, sState);
                    oStateMachine.StateTransition(sState, nvNotDR, sState);
                    oStateMachine.StateTransition(sState, nvToFirst, "state_0");
                    int iNextIdx = iIdx + 1;
                    NodeVector nvSelNext = dicAddresses[iNextIdx];
                    NodeVector nvOutOfSync = oInputStage.OutputBus.DataReady & (!nvSelFirst & !nvSelNext);
                    oStateMachine.StateTransition(sState, nvOutOfSync, "state_reset");
                }
                iIdx++;
            }

            int iPreFinalState = dicAddresses.Count - 2;
            string sPreFinal = "state_" + iPreFinalState.ToString();
            NodeVector nvSelFinal = dicAddresses[dicAddresses.Count - 1];
            NodeVector nvToStall = oInputStage.OutputBus.DataReady & OutputBus.Stall & nvSelFinal;
            NodeVector nvToFinal = oInputStage.OutputBus.DataReady & !OutputBus.Stall & nvSelFinal;
            oStateMachine.StateTransition(sPreFinal, nvToStall, "state_stall");
            oStateMachine.StateTransition(sPreFinal, nvToFinal, "state_final");
            oStateMachine.StateTransition("state_stall", !OutputBus.Stall, "state_stall_final");
            oStateMachine.StateTransition("state_stall", OutputBus.Stall, "state_stall");
            
            oStateMachine.StateTransition("state_final", nvToFirst, "state_0");
            oStateMachine.StateTransition("state_reset", !nvToFirst, "state_reset");

            oStateMachine.StateTransition("state_stall_final", nvToFirst, "state_0");
            oStateMachine.StateTransition("state_reset", !nvToFirst, "state_reset");

            oOutputStage.InputBus.DataReady.Join(oStateMachine.StateCurrent("state_final") | oStateMachine.StateCurrent("state_stall_final"));

            NodeVector nvStallPreFinal = OutputBus.Stall & oStateMachine.StateCurrent(sPreFinal);
            oInputStage.OutputBus.Stall.Join(Operators.OR(this,nvStallPreFinal,oStateMachine.StateCurrent("state_stall"),oStateMachine.StateCurrent("state_stall_final")));

            //oInputStage.OutputBus.Data.ConnectSubset(oOutputStage.InputBus.Data,iLSB,OutputBus.Data.Width - 1);
        }

    }
}
